﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;

namespace SSFT
{
	public partial class AboutForm : Form
	{
		Game game;

		public AboutForm()
		{
			InitializeComponent();

			// A funny little "easter egg" game similar to the one in WinRAR.
			gameCanvas.Hide();
			game = new Game(
				new Action(delegate() { gameCanvas.Hide(); }),
				new Action(delegate() { gameCanvas.Refresh(); }),
				gameCanvas.Width, gameCanvas.Height,
				global::SSFT.Properties.Resources.AboutIcon);
		}

		private void AboutForm_FormClosing(object sender, FormClosingEventArgs e)
		{
			this.Hide();
			e.Cancel = true;
		}

		private void pictureBox2_MouseDown(object sender, MouseEventArgs e)
		{
			// When the user presses the mouse within the game
			// area, show the game canvas and start the game.
			gameCanvas.Show();
			game.Start();

			// Also let the game have the mouse down event.
			game.MouseDown(e);
		}

		// This is the game paint callback called whenever
		// the game has competed a game step.
		private void GameCanvas_Paint(object sender, PaintEventArgs e)
		{
			game.Paint(e.Graphics);
		}

		private void GameCanvas_MouseDown(object sender, MouseEventArgs e)
		{
			game.MouseDown(e);
		}

		private void GameCanvas_MouseUp(object sender, MouseEventArgs e)
		{
			game.MouseUp(e);
		}

		private void GameCanvas_MouseMove(object sender, MouseEventArgs e)
		{
			game.MouseMove(e);
		}

		// A funny little game simulating a set of balls colliding with
		// each other in a very simple way bouncing off the game canvas walls.
		private class Game
		{
			// Game state:
			bool is_running;
			bool is_resetting;

			// Reset state:
			Timer reset_timer;
			const int ResetInterval = 3000;
			Action reset_action;
			Vec2 reset_force;
			const double ResetRadius = 8.0;
			const double ResetSpeed = 500.0;

			// Game step state:
			Timer cycle_timer;
			const int CycleInterval = 10;
			Stopwatch cycle_stopwatch;

			// Scene state:
			Arena arena;
			const double ArenaBounceCoeff = 0.9;
			List<Ball> balls;
			Ball focus_ball;
			Vec2 initial_position;
			const int NumBalls = 3;

			// Bungee state:
			Vec2 bungee_force;
			const double BungeeForceCoeff = 2.5;

			// Input state:
			bool mouse_pressed;
			Vec2 mouse_position;

			// Paint callback:
			Action paint_action;

			public Game(Action reset_action, Action paint_action, int width, int height, Image image)
			{
				// A timer used for resetting the game after a certain time.
				this.reset_timer = new Timer();
				this.reset_timer.Interval = ResetInterval;
				this.reset_timer.Tick += new EventHandler(reset_timer_Tick);
				this.reset_action = reset_action;

				// A timer used for doing a game step with a certain interval.
				this.cycle_timer = new Timer();
				this.cycle_timer.Interval = CycleInterval;
				this.cycle_timer.Tick += new EventHandler(cycle_timer_Tick);

				// A stopwatch used for timing the game steps.
				this.cycle_stopwatch = new Stopwatch();

				// Set the game arena.
				this.arena = new Arena(width, height);

				// Create the user ball.
				this.initial_position = new Vec2(image.Width / 2.0, image.Height / 2.0);
				this.focus_ball = new Ball(initial_position, image.Width / 2.0, 1.0, image);
				this.balls = new List<Ball>();
				this.balls.Add(focus_ball);

				// Create some randomly positioned balls.
				Random t = new Random();
				for (int i = 0; i < NumBalls; i++)
				{
					double r = t.NextDouble() * 10.0 + 15.0;
					double m = r * 0.01;
					double x = t.NextDouble() * (width - 2.0 * r) + r;
					double y = t.NextDouble() * (height - 2.0 * r) + r;
					this.balls.Add(new Ball(new Vec2(x, y), r, m, image));
				}

				// Reset the bungee force exerted by the user mouse.
				this.bungee_force = new Vec2(0.0, 0.0);

				// Set the paint event callback.
				this.paint_action = paint_action;
			}

			public void Start()
			{
				// Set the game state.
				is_running = true;
				is_resetting = false;

				// Start the timers.
				reset_timer.Start();
				cycle_timer.Start();
				cycle_stopwatch.Start();
			}

			// This is the game reset callback called whenever the
			// reset timer has timed out, because the user hasn't
			// given any input for a certain period of time.
			private void reset_timer_Tick(object sender, EventArgs e)
			{
				this.is_resetting = true;
				this.reset_force = new Vec2(0.0, 0.0);
			}

			// This is the game step callback called by the game step
			// timer at repeating intervals.
			private void cycle_timer_Tick(object sender, EventArgs e)
			{
				// Get the time since the last game step.
				double dt = cycle_stopwatch.Elapsed.TotalSeconds;
				cycle_stopwatch.Reset();
				cycle_stopwatch.Start();

				if (this.is_resetting)
				{
					// If the game is resetting, apply a reset force
					// on the user ball.
					focus_ball.ApplyForce(reset_force, dt);
				}
				else
				{
					if (this.mouse_pressed)
					{
						// If the user is pressing the mouse,
						// apply a bungee force on the user ball.
						Vec2 diff = this.mouse_position - this.focus_ball.Position;
						this.bungee_force = diff * BungeeForceCoeff;
						focus_ball.ApplyForce(bungee_force, dt);
					}
				}

				// Check for ball-to-ball collisions and
				// ball-to-arena collisions and move each ball.
				for (int i = 0; i < this.balls.Count; i++)
				{
					Ball ball = balls[i];
					ball.CheckCollide(arena);
					for (int j = i + 1; j < this.balls.Count; j++)
						if (!(ball == focus_ball && this.is_resetting))
							ball.CheckCollide(this.balls[j]);
					ball.Move(dt);
				}

				// If the game is resetting, the user ball should be
				// pulled back to its initial position in a graceful way.
				if (is_resetting)
				{
					Vec2 p = (initial_position - focus_ball.Position);
					Vec2 u = focus_ball.Velocity.Projected(p);
					Vec2 v = focus_ball.Velocity.Projected(p.Normal);
					Vec2 t = p.Unit * ResetSpeed;
					reset_force = (t - u) * 1.0 + -v * 10.0;
					if (focus_ball.Position.InsideCircle(initial_position, ResetRadius))
					{
						focus_ball.Position = initial_position;
						foreach (Ball ball in this.balls)
							ball.Velocity = new Vec2(0.0, 0.0);
						is_running = false;
					}
				}

				// If the game is not running, stop the timers
				// and call the game has reset callback.
				if (!is_running)
				{
					reset_timer.Stop();
					cycle_timer.Stop();
					cycle_stopwatch.Reset();
					reset_action();
				}

				// Do a paint callback.
				paint_action();
			}

			public void MouseDown(MouseEventArgs e)
			{
				if (e.Button == MouseButtons.Left)
				{
					this.mouse_pressed = true;
					this.mouse_position = new Vec2((double)e.X, (double)e.Y);
				}
			}

			public void MouseUp(MouseEventArgs e)
			{
				if (e.Button == MouseButtons.Left)
				{
					this.mouse_pressed = false;
					this.mouse_position = new Vec2((double)e.X, (double)e.Y);
				}
			}

			public void MouseMove(MouseEventArgs e)
			{
				if (this.mouse_pressed)
				{
					this.mouse_position = new Vec2((double)e.X, (double)e.Y);

					// We have user input, prevent the game
					// reset timer from timing out.
					this.is_resetting = false;
					this.reset_timer.Stop();
					this.reset_timer.Start();
				}
			}

			public void Paint(Graphics g)
			{
				// Paint the arena.
				arena.Paint(g);

				// If the mouse is pressed, paint the bungee.
				if (this.mouse_pressed && !this.is_resetting)
					g.DrawLine(Pens.Black, focus_ball.Position.Point, this.mouse_position.Point);

				// Paint the balls.
				foreach (Ball ball in balls)
					ball.Paint(g);
			}

			private class Arena
			{
				double width;
				double height;

				public Arena(double width, double height)
				{
					this.width = width;
					this.height = height;
				}

				public double Width
				{
					get { return this.width; }
				}

				public double Height
				{
					get { return this.height; }
				}

				public void Cycle(double dt)
				{
				}

				public void Paint(Graphics g)
				{
					g.FillRectangle(SystemBrushes.Control, 0, 0, (int)this.width, (int)this.height);
				}
			}

			private class Ball
			{
				Vec2 position;
				Vec2 velocity;
				double radius;
				double mass;

				Image image;

				public Ball(Vec2 position, double radius, double mass, Image image)
				{
					this.position = position;
					this.velocity = new Vec2(0.0, 0.0);
					this.radius = radius;
					this.mass = mass;
					this.image = image;
				}

				public Vec2 Position
				{
					get { return this.position; }
					set { this.position = value; }
				}

				public Vec2 Velocity
				{
					get { return this.velocity; }
					set { this.velocity = value; }
				}

				public double Radius
				{
					get { return this.radius; }
					set { this.radius = value; }
				}

				public double Mass
				{
					get { return this.mass; }
					set { this.mass = value; }
				}

				public void Cycle(double dt)
				{
					this.Move(dt);
				}

				public void Paint(Graphics g)
				{
					g.DrawImage(this.image,
						(int)(position[0] - radius), (int)(position[1] - radius),
						(int)(2.0 * radius), (int)(2.0 * radius));
				}

				// Ball collisions with walls simply negate the ball's velocity
				// vector in the direction of the normal of the wall,
				// also moving the ball out of the wall.
				public void CheckCollide(Arena arena)
				{
					double w = arena.Width, h = arena.Height;
					double x = position[0], y = position[1];
					double r = radius;

					if (x - r < 0)
					{
						velocity[0] = -velocity[0] * ArenaBounceCoeff;
						position[0] = 0 + r;
					}
					if (y - r < 0)
					{
						velocity[1] = -velocity[1] * ArenaBounceCoeff;
						position[1] = 0 + r;
					}
					if (x + r > w)
					{
						velocity[0] = -velocity[0] * ArenaBounceCoeff;
						position[0] = w - r;
					}
					if (y + r > h)
					{
						velocity[1] = -velocity[1] * ArenaBounceCoeff;
						position[1] = h - r;
					}
				}

				// The method of solving the collision between balls here,
				// is the preservation of energy and the preservation of
				// linear momentum in a moving system of two masses derived
				// from the following formulae:
				//
				// m1v1+m2v2=m1u1+m2u2 (1)
				// (1/2)m1v1^2+(1/2)m2v2^2=(1/2)m1u1^2+(1/2)m2u2^2 (2)
				// where
				// m1 is the mass of object 1
				// m2 is the mass of object 2
				// v1 is the initial velocity of object 1
				// v2 is the initial velocity of object 2
				// u1 is the final velocity of object 1
				// u2 is the final velocity of object 2
				//
				// Also making sure to move the balls out of eachother since
				// this is not a very fancy collision handler.
				public void CheckCollide(Ball b)
				{
					Vec2 p = b.Position - this.Position;
					if (p.Length < this.Radius + b.Radius)
					{
						double m1 = this.Mass, m2 = b.Mass;
						Vec2 v1 = this.Velocity.Projected(p), v2 = b.Velocity.Projected(p);
						Vec2 u1 = (v1 * (m1 - m2) + v2 * m2 * 2.0) / (m1 + m2);
						Vec2 u2 = (v2 * (m2 - m1) + v1 * m1 * 2.0) / (m1 + m2);
						this.Velocity = u1 + this.Velocity.Projected(p.Normal);
						b.Velocity = u2 + b.Velocity.Projected(p.Normal);

						double r1 = this.Radius, r2 = b.Radius;
						Vec2 p1 = this.Position, p2 = b.Position;
						double t = ((r1 + r2) - p.Length) / 2.0;
						this.Position = p1 + -p.Unit * t;
						b.Position = p2 + p.Unit * t;
					}
				}

				public void Move(double dt)
				{
					this.position = this.position + this.velocity * dt;
				}

				public void ApplyForce(Vec2 force, double dt)
				{
					this.velocity = this.velocity + (force * dt) / this.mass;
				}
			}

			private class Vec2
			{
				double[] v_m;

				public Vec2(double v1, double v2)
				{
					v_m = new double[2];
					v_m[0] = v1;
					v_m[1] = v2;
				}

				public static Vec2 operator +(Vec2 v1, Vec2 v2)
				{
					return new Vec2(v1[0] + v2[0], v1[1] + v2[1]);
				}

				public static Vec2 operator -(Vec2 v1, Vec2 v2)
				{
					return new Vec2(v1[0] - v2[0], v1[1] - v2[1]);
				}

				public static Vec2 operator *(Vec2 v, double k)
				{
					return new Vec2(v[0] * k, v[1] * k);
				}

				public static Vec2 operator /(Vec2 v, double k)
				{
					return new Vec2(v[0] / k, v[1] / k);
				}

				public static Vec2 operator -(Vec2 v)
				{
					return new Vec2(-v[0], -v[1]);
				}

				public Vec2 Projected(Vec2 v)
				{
					double l = v.Length, t = v.Dot(this) / (l * l);
					return v * t;
				}

				public double Dot(Vec2 v)
				{
					return this[0] * v[0] + this[1] * v[1];
				}

				public double Cross(Vec2 v)
				{
					return this[0] * v[1] - this[1] * v[0];
				}

				public Vec2 Normal
				{
					get { return new Vec2(-this[1], this[0]); }
				}

				public double Length
				{
					get { return Math.Sqrt(this[0] * this[0] + this[1] * this[1]); }
				}

				public Vec2 Unit
				{
					get { return new Vec2(this[0] / Length, this[1] / Length); }
				}

				public bool InsideCircle(Vec2 v, double r)
				{
					return (this - v).Length < r;
				}

				public Point Point
				{
					get { return new Point((int)this[0], (int)this[1]); }
				}

				public double this[int i]
				{
					get { return v_m[i]; }
					set { v_m[i] = value; }
				}
			}
		}
	}
}
